﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using ProjectHopur23.Repositories;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR;
using ProjectHopur23.Models;

namespace ProjectHopur23.Controllers
{
	public class LudoHub : GameHub
	{
		#region Constructors
		public LudoHub()
		{
			rep = new Repository();
		}

		public LudoHub(IRepository r)
		{
			rep = r;
		}
		#endregion

		#region Properties
		Random die = new Random();
		#endregion

		#region DatabaseFunctions
		public IEnumerable<LudoState> GetAllLudoStatesById(int activeGameId)
		{
			IEnumerable<LudoState> theStates = (from l in rep.GetAllLudoStates()
												where l.activeGameId == activeGameId
												select l);

			return theStates;
		}

		public IEnumerable<LudoState> GetGameStates(string activeGameId)
		{
			return GetAllLudoStatesById(Convert.ToInt32(activeGameId));
		}
		#endregion

		#region Functions
		/// <summary>
		/// This function initiates the turn for active player
		/// </summary>
		public void StartTurn(string activeGameId, string userName)
		{
			int intActiveGameId = Convert.ToInt32(activeGameId);
			ActiveGame theGame = rep.GetAllActiveGames().First(a => a.ID == intActiveGameId);
			if(theGame == null || theGame.isFinished == true || theGame.isFull == false)
			{
				return;
			}

			int intUserId = (rep.GetAllUsers().First(u => u.UserName == userName)).UserId;

			// Check if active player
			if(IsActivePlayer(intUserId, intActiveGameId))
			{
				// Check if die has been rolled. If it has not been rolled then roll die.
				DieValue currentThrow = rep.GetAllDieValues().First(c => c.activeGameId == intActiveGameId);
				if(!currentThrow.dieRolled)
				{
					// Get outcome from die
					int outcomeFromDie = RollDie(activeGameId);
					// Check if not a valid move
					if (!ValidMoveExists(intActiveGameId ,theGame.activePlayerId, outcomeFromDie))
					{
						theGame.activePlayerId = SwitchActivePlayer(theGame);
						currentThrow.dieRolled = false;
						rep.Save();
                        // Inform clients who's turn it is
                        Clients.Group(activeGameId).getNextPlayer(theGame.activePlayerId);
					}

					// Call client to display value of die
					Clients.Group(activeGameId).dieWasRolled(outcomeFromDie);
				}
			}
		}

		/// <summary>
		/// Rolls the die
		/// </summary>
		public int RollDie(string activeGameId)
		{
			int intActiveGameId = Convert.ToInt32(activeGameId);
			int dieValue = die.Next(1, 7);
			//Needs the function in the interface
			DieValue theDieValue = (from d in rep.GetAllDieValues()
						where d.activeGameId == intActiveGameId
						select d).SingleOrDefault();

			theDieValue.dieRolled = true;
			theDieValue.dieValue = dieValue;
			rep.Save();
			return dieValue;
		}

		public int GetDieValue(string activeGameId)
		{
			int theDieValue = (from d in rep.GetAllDieValues()
						where d.activeGameId == Convert.ToInt32(activeGameId)
						select d).SingleOrDefault().dieValue;

			return theDieValue;
		}

		/// <summary>
		/// Checks if a valid move exists for the active player
		/// </summary>
		public bool ValidMoveExists(int gameId, int aPId, int dieValue)
		{
			//Get all 4 Ludostates belonging to the active player in this active game
			IEnumerable<LudoState> pieces = (from p in rep.GetAllLudoStates()
								where p.playerId == aPId
								where p.activeGameId == gameId
								select p).ToList();

			//Check if there is a valid move for each of the 4 pieces
			foreach (LudoState p in pieces)
			{
				//If a valid move exists for any piece, a valid move exists for the player
				if(IsMoveValid(GetDestinationCellId(dieValue, p), p))
				{
					return true;
				}
			}

			//If no valid move exists
			return false;
		}
        
		/// <summary>
		/// Calls
		/// GetDestinationCellId()
		/// CellIsEmpty()
		/// IsEnemy()
		/// </summary>
		public bool IsMoveValid(int theDestination, LudoState thePiece)
		{
			if(CellIsEmpty(theDestination, thePiece))
			{
				return true;
			}
			else if(IsEnemy(theDestination, thePiece))
			{
				return true;
			}
			return false;
		}

		/// <summary>
		///			Green	Blue	Yellow	Blue
		///	Home	100-103	104-107	108-111	112-115
		///	Start	1		14		27		40
		/// Lane	60-64	70-74	80-84	90-94
		/// 
		/// </summary>
		public int GetDestinationCellId(int dieValue, LudoState thePiece)
		{
			if(thePiece.cellId > 199)
			{
				return thePiece.cellId;
			}

			if(thePiece.cellId >= 100 && thePiece.cellId < 150)
			{
				// If we are at home and didn't roll a 6, let's stay home
				if(dieValue != 6)
				{
					return thePiece.cellId;
				}

				// We rolled a 6? Now we can go to the start of our area
				return (1 + (thePiece.playerId * 13));
			}

			int playerOffset = 60 + (10*thePiece.playerId);
			int boundary = playerOffset + 5;

			// Are we in a victory lane?
			if(thePiece.cellId >= 60)
			{
				int newDest = thePiece.cellId + dieValue;
				// If we jumped past the victory cell
				if(newDest > boundary)
				{
					int jumpOffset = newDest - boundary;
					return boundary - jumpOffset;
				}
				// we won
				else if(newDest == boundary)
				{
					return 150;
				}
				else
				{
					return newDest;
				}
			}

			// We are still on the main board, let's check the piece counter
			int newCounter = thePiece.pieceCounter - dieValue;
			// If newCounter is negative it means we should enter the victory lane
			if(newCounter < 0)
			{
				// we jumped into the victory cell
				if(newCounter == -6)
				{
					return 150;
				}

				return playerOffset + (-newCounter) - 1;
			}

			return ((thePiece.cellId + dieValue) % 52);
		}

		/// <summary>
		/// Gets cellId from GetDestinationCellId()
		/// Checks if the destination cell is empty or not
		/// </summary>
		public bool CellIsEmpty(int cellId, LudoState thePiece)
		{
			//Get active game id
			int aGId = thePiece.activeGameId;

			//Get state of the destination cell
			LudoState destCell = (from d in rep.GetAllLudoStates()
						where d.activeGameId == aGId 
						where d.cellId == cellId
									select d).ToList().SingleOrDefault();
			//If state is null, cell is empty
			if(destCell == null)
			{
				return true;
			}
			else
			{
				return false;
			}
		}

		/// <summary>
		/// Gets destinationCellId from GetDestinationCellId()
		/// Moves the chosen piece to its designated destination
		/// updates database accordingly
		/// </summary>
		public void MovePiece(int dieValue, int destinationCellId, LudoState thePiece)
		{
			if(CellIsEmpty(destinationCellId, thePiece))
			{
				thePiece.cellId = destinationCellId;
				
				thePiece.pieceCounter -= dieValue;

				if (thePiece.cellId == 150)
				{
					thePiece.cellId = thePiece.pieceId + 200;
				}
			}
			else if(IsEnemy(destinationCellId, thePiece))
			{
				LudoState sentHome = (	from l in rep.GetAllLudoStates()
										where l.activeGameId == thePiece.activeGameId
										where l.cellId == destinationCellId
										select l).ToList().SingleOrDefault();
				SendHome(sentHome);

				thePiece.cellId = destinationCellId;
				
				thePiece.pieceCounter -= dieValue;

				if (thePiece.cellId == 150)
				{
					thePiece.cellId = thePiece.pieceId + 200;
				}
			}
			rep.Save();
		}

		 /// <summary>
		/// Is called by the client when the user clicks a cell
		/// 
		/// * Can the piece move?
		/// * How far will it move?
		/// * Will it be moved from a starting state?
		/// * Or onto a final lane?
		/// * Or will it send an opponent back home?
		/// 
		/// This function takes care of most of the stuff
		/// that happens in Ludo
		/// </summary>
		public void ClickCell(string activeGameId, string userName, string cellId)
		{	
			int theActiveGameId = Convert.ToInt32(activeGameId);

			bool dieHasBeenRolled = (from d in rep.GetAllDieValues()
							where d.activeGameId == theActiveGameId
							select d).ToList().SingleOrDefault().dieRolled;

			if(dieHasBeenRolled)
			{
				int theCellId = Convert.ToInt32(cellId);

				LudoState theClickedPiece = (from l in rep.GetAllLudoStates()
								where l.cellId == theCellId
								where l.activeGameId == theActiveGameId
								select l).SingleOrDefault();

				if(theClickedPiece != null)
				{
					//TODO: Finish
					int theUserId = (from u in rep.GetAllUsers()
							where u.UserName == userName
							select u).SingleOrDefault().UserId;
						
					if(IsActivePlayer(theUserId, theActiveGameId))
					{	
						int thePlayerId = (from p in rep.GetAllPlayers()
									where p.activeGameId == theActiveGameId
									where p.userId == theUserId
												select p).SingleOrDefault().playerId;
						if(thePlayerId == theClickedPiece.playerId)
						{
							int theDieValue = (from d in rep.GetAllDieValues()
										where d.activeGameId == theActiveGameId
										select d).SingleOrDefault().dieValue;
			
							int theDestinationCellId = GetDestinationCellId(theDieValue, theClickedPiece);

							if(IsMoveValid(theDestinationCellId, theClickedPiece))
							{
								//Move the piece
								MovePiece(theDieValue, theDestinationCellId, theClickedPiece);

								if(CheckForWin(theActiveGameId, thePlayerId))
								{
									//Update the winner's score
									//Inform the clients of the victory
									ActiveGame theActiveGame = (from a in rep.GetAllActiveGames()
													where a.ID == theActiveGameId
													select a).SingleOrDefault();

									PostGameUpdate(theActiveGame, theUserId);
									Clients.Group(activeGameId).cellClicked(cellId, thePlayerId, theClickedPiece.cellId, true, userName);
								}
								else if(theDieValue != 6)
								{
									//The player is done
									ActiveGame theActiveGame = (from a in rep.GetAllActiveGames()
													where a.ID == theActiveGameId
													select a).SingleOrDefault();

									theActiveGame.activePlayerId = SwitchActivePlayer(theActiveGame);
                                    // Inform clients who's turn it is
                                    Clients.Group(activeGameId).getNextPlayer(theActiveGame.activePlayerId);

									DieValue changedDieValue = (from d in rep.GetAllDieValues()
													where d.activeGameId == theActiveGameId
													select d).SingleOrDefault();
				
									changedDieValue.dieRolled = false;
									rep.Save();

									//Inform the clients of the changes made
									Clients.Group(activeGameId).cellClicked(cellId, thePlayerId, theClickedPiece.cellId, false, userName);
								}
								else if(theDieValue == 6)
								{
									//The player got a 6 and he should do again
									DieValue changedDieValue = (from d in rep.GetAllDieValues()
													where d.activeGameId == theActiveGameId
													select d).SingleOrDefault();

									changedDieValue.dieRolled = false;
									rep.Save();

									//Inform the clients of the changes made
									Clients.Group(activeGameId).cellClicked(cellId, thePlayerId, theClickedPiece.cellId, false, userName);
								}
							}
						}
					}
				}
			}
		}

		/// <summary>
		/// Return's true if player has won else return false
		/// </summary>
		public bool CheckForWin(int activeGameId, int playerId)
		{
			IEnumerable<LudoState> currentLudoState = from c in rep.GetAllLudoStates()
									where c.activeGameId == activeGameId
									where c.playerId == playerId
									select c;

			// Loop through all pieces and. If some piece is not at "End >= 200" then return false
			foreach (LudoState c in currentLudoState)
			{
				if(c.cellId < 200)
				{
					return false;
				}
			}

			// All pieces are at "End". We have winner.
			return true;
		}

		/// <summary>
		/// Updates the winner score and set game finished variable equal to true
		/// CellClicked will call rep.save. TODO: Implement
		/// </summary>
		public void PostGameUpdate(ActiveGame theGame, int winnerId)
		{
			//TODO: Implement
			UserProfile theWinner = (from u in rep.GetAllUsers()
							where u.UserId == winnerId
							select u).SingleOrDefault();

			// Incrememt winner score
			theWinner.score += 20;
			// Declare game as finished
			theGame.isFinished = true;
			// Save to database
			rep.Save();
		}

		/// <summary>
		/// Check if there is an enemy piece at destination cell
		/// </summary>
		public bool IsEnemy(int cellId, LudoState thePiece)
		{
			// Current playerId
			int currentPlayerId = thePiece.playerId;
			// Player and piece at destination
			LudoState playerAtDestination = (from l in rep.GetAllLudoStates()
							where l.activeGameId == thePiece.activeGameId
							where l.cellId == cellId
							select l).ToList().SingleOrDefault();

			// Check if it is enemy at destination
			if(currentPlayerId != playerAtDestination.playerId)
			{
				return true;
			}
			else
			{
				return false;
			}
		}

		public void SendHome(LudoState theState)
		{
			int theCellId = theState.cellId;
			
			theState.cellId = theState.pieceId + 100;
			// Set piece counter equal to 56 because it is at home location
			theState.pieceCounter = 56;
			rep.Save();
			// Convert activeGameId to string to pass to client
			string stringActiveGameId = Convert.ToString(theState.activeGameId);
			// Get the current userId

			// Call client click cell to repaint the gameboard
			Clients.Group(stringActiveGameId).cellClicked(theCellId, theState.playerId, theState.cellId, false, "No winner");
		}
		#endregion
	}
}
